#include "Calculator.h"
using namespace std;

#define DEBUG 0

#include <iostream>

Calculator::Calculator(){

}

Calculator::Calculator(string input){
    setInput(input);
    init();
}

void Calculator::setInput(string input){
    this->input = input;
}

void Calculator::init(){
    op["^"] = 2;
    op["*"] = 1;
    op["/"] = 1;
    op["+"] = 0;
    op["-"] = 0;
}


double Calculator::calculate(){
#if DEBUG
    cout << endl <<"Begin tokenize" << endl;
#endif
    tokenize();
#if DEBUG
    for(size_t i=0; i<tokens.size(); ++i){
        cout << tokens.at(i).value << "\t" << tokens.at(i).type << endl;
    }
#endif
#if DEBUG
    cout << endl << "Begin shunting yard" << endl;
#endif
    shuntingYard();
#if DEBUG
    cout << endl << "Begin evaluate" << endl;
#endif
    return evaluate();
}

void Calculator::tokenize(){
    string t("");
    bool number = false;
    bool sign = false;
    bool function = false;
    for(size_t i=0; i<input.size(); ++i){
        t = "";
        if(input.at(i)=='!' || input.at(i)=='*' || input.at(i)=='+' ||
                input.at(i)=='/' || input.at(i)=='^'){//character is !,*,+,/ or ^
            t = input.at(i);
            Token token(t, "operator");
            tokens.push_back(token);
        } else if(input.at(i)=='-'){//character is -
            if(i>0 && (input.at(i-1)!='*' && input.at(i-1)!='+' &&
                input.at(i-1)!='/' && input.at(i-1)!='^' && input.at(i-1)!='-')){
                t = input.at(i);
                Token token(t, "operator");
                tokens.push_back(token);
            } else {
                sign = true;
            }
        } else if(input.at(i)=='('){//character is (
            t = input.at(i);
            Token token(t, "left_parenthesis");
            tokens.push_back(token);
        } else if(input.at(i)==')'){//character is )
            t = input.at(i);
            Token token(t, "right_parenthesis");
            tokens.push_back(token);
        } else if(input.at(i)==','){//character is ,
            t = input.at(i);
            Token token(t, "separator");
            tokens.push_back(token);
        }
        t = "";
        while((input.at(i)>='0' && input.at(i)<='9') || input.at(i)=='.' || sign){//character is a part of a number
            t += input.at(i++);
            number = true;
            sign = false;
            if(i>=input.size()){
                break;
            }
        }
        if(number){
            Token token(t, "number");
            tokens.push_back(token);
            number = false;
            --i;
            t = "";
            if(i>=input.size()){
                break;
            }
        }
        while ((input.at(i)>='A' && input.at(i)<='Z') || (input.at(i)>='a' && input.at(i)<='z')){//character is part of a function
            t += input.at(i++);
            function = true;
            if(i>=input.size()){
                break;
            }
        }
        if(function){
            Token token(t, "function");
            tokens.push_back(token);
            function = false;
            --i;
        }
    }
}

void Calculator::shuntingYard(){
    stack<bool> wereValues;
    stack<unsigned int> argCount;
    stack<Token> s;
    
    for(size_t i=0; i<tokens.size(); ++i){
#if DEBUG
    cout << "Handling token: " << tokens.at(i).value << endl;
#endif
        if(tokens.at(i).type == "number"){
            rpn.push(tokens.at(i));
            if(!wereValues.empty()){
                wereValues.pop();
                wereValues.push(true);
            }
        } else if(tokens.at(i).type == "function"){
            s.push(tokens.at(i));
            argCount.push(0);
            if(!wereValues.empty()){
                wereValues.pop();
                wereValues.push(true);
            }
            wereValues.push(false);
        } else if(tokens.at(i).type == "separator"){
            while(s.size()>0 && s.top().type != "left_parenthesis"){
                rpn.push(s.top());
                s.pop();
            }
            if(s.size() == 0){
                cerr << "Error: misplaced separator or parenthesis" << endl;
            }

            bool w = wereValues.top();
            wereValues.pop();
            if(w){
                ++argCount.top();
            }
            wereValues.push(false);
        } else if(tokens.at(i).type == "operator"){
            if(unary(tokens.at(i).value)){
                rpn.push(tokens.at(i));
            } else { //binary
                while(!s.empty() && s.top().type == "operator" && ((associative(tokens.at(i))<=0 && comparePrecedence(tokens.at(i), s.top())<=0) ||
                        (associative(tokens.at(i))>0 && comparePrecedence(tokens.at(i), s.top())<0))){
                    rpn.push(s.top());
                    s.pop();
                }
                s.push(tokens.at(i));
            }
        } else if(tokens.at(i).type == "left_parenthesis"){
            s.push(tokens.at(i));
        } else if(tokens.at(i).type == "right_parenthesis"){
            while(s.size() > 0 && s.top().type != "left_parenthesis"){
                rpn.push(s.top());
                s.pop(); //normaal zouden enkel operators gepoped moeten worden, is dit een probleem?
            }
            if(s.size() == 0){
                cerr << "Error: misplaced parenthesis" << endl;
            }
            s.pop();
            if(!s.empty() && s.top().type == "function"){
                unsigned int a = argCount.top();
                argCount.pop();
                bool w = wereValues.top();
                wereValues.pop();
                if(w){
                    ++a;
                }
                ostringstream strA;
                strA << a;
                s.top().type += strA.str();
                rpn.push(s.top());
                s.pop();
            }
        }
    }
    while(!s.empty()){
        rpn.push(s.top());
        s.pop();
    }

#if DEBUG
    cout << "Begin output queue:" << endl;
    queue<Token> t(rpn);
    while(!t.empty()){
        cout << t.front().value << endl;
        t.pop();
    }
    cout << "End output queue." << endl;
#endif
}

double Calculator::evaluate(){
    stack<Token> s;
    while(!rpn.empty()){
#if DEBUG
    cout << "Handling token: " << rpn.front().value << endl;
#endif
        if(rpn.front().type == "number"){
            s.push(rpn.front());
        } else if(rpn.front().type == "operator"){
            double result;
            if(unary(rpn.front().value)){
                string strA = s.top().value;
                s.pop();
                double a;
                istringstream inA(strA);
                inA >> a;
                if(rpn.front().value == "!"){
                    result = factorial(a);
                }
            } else { //binary
                string strB = s.top().value;
                s.pop();
                string strA = s.top().value;
                s.pop();
                double a, b;
                istringstream inA(strA), inB(strB);
                inA >> a;
                inB >> b;
                if(rpn.front().value == "+"){
                    result = a+b;
                } else if(rpn.front().value == "-"){
                    result = a-b;
                } else if(rpn.front().value == "/"){
                    result = a/b;
                } else if(rpn.front().value == "*"){
                    result = a*b;
                } else if(rpn.front().value == "^"){
                    result = pow(a,b);
                }
            }
            
            ostringstream out;
            out << result;
            Token res (out.str(), "number");
            s.push(res);
        } else if(rpn.front().type.compare(0, 8, "function") == 0){
            string strArguments = rpn.front().type.substr(8);
            istringstream inArg(strArguments);
            unsigned int arguments;
            inArg >> arguments;
            deque<double> arg;

            for(size_t i=0; i<arguments; ++i){
                string value = s.top().value;
                istringstream inVal(value);
                double val;
                inVal >> val;
                arg.push_front(val);
                s.pop();
            }

            double result;
            if(rpn.front().value == "min"){
                result = min(arg);
            } else if(rpn.front().value == "max"){
                result = max(arg);
            } else if(rpn.front().value == "log"){
                result = log(arg);
            } else if(rpn.front().value == "root"){
                result = root(arg);
            }

            ostringstream out;
            out << result;
            Token res (out.str(), "number");
            s.push(res);
        }
        rpn.pop();
    }
#if DEBUG
    cout << "Result: " << s.top().value << endl;
#endif
    istringstream iss(s.top().value);
    double result;
    iss >> result;
    return result;
}

bool Calculator::unary(string op){
    return (op=="!");
}

double Calculator::factorial(double a){
    double result = 1;
    for(size_t i=2; i<=a; ++i){
        result *= i;
    }
    return result;
}

double Calculator::min(deque<double> d){
    return *min_element(d.begin(), d.end());
}

double Calculator::max(deque<double> d){
    return *max_element(d.begin(), d.end());
}

double Calculator::log(deque<double> d){
    if(d.size() == 1){
        return log10(d.at(0));
    } else {
        return std::log(d.at(0))/std::log(d.at(1));
    }
}

double Calculator::root(deque<double> d){
    if(d.size() == 1){
        return pow(d.at(0), 0.5);
    } else {
        return pow(d.at(0), 1/d.at(1));
    }
}

double Calculator::sin(deque<double> d){
    return std::sin(d.at(0));
}

double Calculator::cos(deque<double> d){
    return std::cos(d.at(0));
}

double Calculator::tan(deque<double> d){
    return std::tan(d.at(0));
}

double Calculator::cot(deque<double> d){
    return std::cos(d.at(0))/std::sin(d.at(0));
}

double Calculator::sinh(deque<double> d){
    return std::sinh(d.at(0));
}

double Calculator::cosh(deque<double> d){
    return std::cosh(d.at(0));
}

double Calculator::tanh(deque<double> d){
    return std::tanh(d.at(0));
}

double Calculator::coth(deque<double> d){
    return std::cosh(d.at(0))/std::sinh(d.at(0));
}

/**
 * 
 * @param op
 * @return  -1 is op is left associative
 *          0 if op is associative
 *          1 is op is right associative
 */
int Calculator::associative(Token op){
    if(op.value == "+" || op.value == "*"){
        return 0;
    } else if(op.value == "-" || op.value == "/"){
        return -1;
    } else if(op.value == "^"){
        return 1;
    }
}

/**
 *
 * @param a
 * @param b
 * @return  -1 if a has a lower precendence than b
 *          0 if a has an equal precedence as b
 *          1 if a has a higher precedence than b
 */
int Calculator::comparePrecedence(Token a, Token b){
    int valueA = op.find(a.value)->second;
    int valueB = op.find(b.value)->second;
    if(valueA == valueB){
        return 0;
    } else if(valueA > valueB){
        return 1;
    } else {
        return -1;
    }
}